package com.sc.common.util;




import cn.hutool.crypto.SecureUtil;
import com.sc.common.enums.CharsetEnum;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import java.io.UnsupportedEncodingException;
import java.net.URLDecoder;
import java.net.URLEncoder;
import java.util.*;

/**
 * 签名工具
 */
public class SignUtil {
    private SignUtil(){}

    static Logger logger = LogManager.getLogger(SignUtil.class);

    /**
     * 除去数组中的空值和签名参数
     * @param sArray 签名参数组
     * @return 去掉空值与签名参数后的新签名参数组
     */
    public static Map<String, String> paraFilter(Map<String, String> sArray) {
        Map<String, String> result = new HashMap<>();
        if (sArray == null || sArray.size() <= 0) {
            return result;
        }
        for (String key : sArray.keySet()) {
            String value = sArray.get(key);
            if (value == null || value.equals("") || key.equalsIgnoreCase("sign")) {
                continue;
            }
            result.put(key, value);
        }
        return result;
    }

    /**
     * 把数组所有元素排序，并按照“参数=参数值”的模式用“&”字符拼接成字符串
     * @param params 需要排序并参与字符拼接的参数组
     * @return 拼接后字符串
     */
    public static String createLinkString(Map<String, String> params) {
        return createLinkString(params, false);
    }

    /**
     * 把数组所有元素排序，并按照“参数=参数值”的模式用“&”字符拼接成字符串
     * @param params 需要排序并参与字符拼接的参数组
     * @param encode 是否需要UrlEncode
     * @return 拼接后字符串
     */
    public static String createLinkString(Map<String, String> params, boolean encode) {
        List<String> keys = new ArrayList<>(params.keySet());
        Collections.sort(keys);
        String result = "";
        for (int i = 0; i < keys.size(); i++) {
            String key = keys.get(i);
            String value = params.get(key);
            if (encode)
                value = urlEncode(value, CharsetEnum.UTF8.getValue());
            if (i == keys.size() - 1) {//拼接时，不包括最后一个&字符
                result = result + key + "=" + value;
            } else {
                result = result + key + "=" + value + "&";
            }
        }
        return result;
    }




    /**
     * URL转码
     * @param content
     * @param charset
     * @return
     */
    private static String urlEncode(String content, String charset) {
        try {
            return URLEncoder.encode(content, charset);
        } catch (UnsupportedEncodingException e) {
            throw new RuntimeException("指定的编码集不对,您目前指定的编码集是:" + charset);
        }
    }


    /**
     * 对参数进行签名
     * @param paraMap
     * @param secretKey
     * @return
     */
    public static Map<String, String> sign(Map<String, String> paraMap, String secretKey) {
        // 时间戳
        paraMap.put("timestamp", String.valueOf(System.currentTimeMillis()));

        // 每次请求的唯一标识
        paraMap.put("nonce",UUID.randomUUID().toString().replaceAll("-","").toUpperCase());

        //除去数组中的空值和签名参数
        Map<String, String> paraMapTemp = paraFilter(paraMap);

        //把数组所有元素，按照“参数=参数值”的模式用“&”字符拼接成字符串
        String linkString = createLinkString(paraMapTemp) + "&secretKey=" + secretKey;

        //生成签名结果
        String signString = SecureUtil.md5(linkString.toUpperCase());

        paraMapTemp.put("sign",signString);

        return paraMapTemp;
    }

    /**
     * 验证承参数是否合法
     * @param map
     * @return
     */
    public static boolean verifyParameter(Map map){
        if(map == null || map.size() == 0){
            return false;
        }else{
            if(!map.containsKey("appId")){
                logger.error("验证失败：缺失[appId]参数");
                return false;
            }

            if(MyStringUtils.isBlank(MyStringUtils.null2String(map.get("appId")))){
                logger.error("验证失败：缺失[appId]参数值");
                return false;
            }

            if(!map.containsKey("sign")){
                logger.error("验证失败：缺失[sign]参数");
                return false;
            }

            if(MyStringUtils.isBlank(MyStringUtils.null2String(map.get("sign")))){
                logger.error("验证失败：缺失[sign]参数值");
                return false;
            }

            if(!map.containsKey("nonce")){
                logger.error("验证失败：缺失[nonce]参数");
                return false;
            }
            if(MyStringUtils.isBlank(MyStringUtils.null2String(map.get("nonce")))){
                logger.error("验证失败：缺失[nonce]参数值");
                return false;
            }


            if(!map.containsKey("timestamp")){
                logger.error("验证失败：缺失[timestamp]参数");
                return false;
            }

            if(MyStringUtils.isBlank(MyStringUtils.null2String(map.get("timestamp")))){
                logger.error("验证失败：缺失[timestamp]参数值");
                return false;
            }
        }
        return true;
    }


    /**
     * 验证签名，该验证必须在verifyParameter方法返回true的基础上进行
     * @param params
     * @param secretKey
     * @return
     */
    public static boolean verifySign(Map<String, String> params, String secretKey) {
        String sign =  params.get("sign");

        //过滤空值、sign
        Map<String, String> sParaNew = paraFilter(params);

        //获取待签名字符串
        String linkString = createLinkString(sParaNew) + "&secretKey=" + secretKey;

        //获得签名验证结果
        String mySign = SecureUtil.md5(linkString).toUpperCase();
        if (mySign.equals(sign)) {
            return true;
        } else {
            logger.warn("验证失败：签名不一致。得到的签名[{}]，计算出来的签名[{}]",sign,mySign);
            return false;
        }
    }


    /**
     * 验证签名是否过期，该验证必须在verifyParameter方法返回true的基础上进行
     * @param timestamp 签名生成时间戳，单位毫秒
     * @param signDuration 签名保留时长，单位分钟
     * @return
     */
    public static boolean verifySignTimeout(long timestamp, long signDuration) {
        long curr = System.currentTimeMillis();
        if (((curr - timestamp) / 1000 / 60 )> signDuration){
            logger.warn("验证失败：该请求的签名已经过期，签名时间[{}]，当前时间[{}]",timestamp,curr);
            return false;
        }
       return true;
    }




    public static void main(String[] args) {
        Map<String,String> parMap = new HashMap<>();
        parMap.put("appId","a");
        parMap.put("companyId","-1");
        parMap.put("color","2");
        parMap.put("b","4");
        System.out.println(SignUtil.sign(parMap,"testSecretKey"));



        Map<String,String> parMap1 = new HashMap<>();
        parMap1.put("companyId","-1");
        parMap1.put("b","4");
        parMap1.put("color","2");
        parMap1.put("appId","a");
        parMap1.put("sign","0a11c6f064a4db1740382eff2960d519");
        parMap1.put("nonce","2207764657701661416");
        parMap1.put("timestamp","1563428721850");
        System.out.println(SignUtil.verifySign(parMap1,"testSecretKey"));

        try {
            String s = URLEncoder.encode("wusongti@163", CharsetEnum.UTF8.getValue());
            System.out.println(s);

            String s1 = URLDecoder.decode(URLDecoder.decode("wusongti%2540163.com", CharsetEnum.UTF8.getValue()),CharsetEnum.UTF8.getValue());
            System.out.println(s1);
        } catch (UnsupportedEncodingException e) {
            e.printStackTrace();
        }
    }
}
